#include "venc.h"
#include "rk_mpi.h"
#include "mpp_buffer.h"
#include "mpp_env.h"
#include "mpp_common.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

MppBufferGroup group;
MppCtx         ctx;
MppApi         *mpi;

void Venc_TransformDmaBufToMppBuf(int fd, MppBuffer *pbuf, int buf_len)
{
    MppBufferInfo info;

    printf("get dma buf fd: %d\n", fd);
    memset(&info, 0, sizeof(MppBufferInfo));
    info.type = MPP_BUFFER_TYPE_EXT_DMA;
    info.fd =  fd;
    info.size = buf_len & 0x07ffffff;
    info.index = (buf_len & 0xf8000000) >> 27;
    mpp_buffer_import(pbuf, &info);
}

static int venc_cfg_setup(Venc_Device_t *device, Venc_Config_t *InitCfg)
{
    MPP_RET ret = 0;
    MppEncCfg cfg;
    int bps;
    int split_mode = 0;
    int split_arg = 0;
    int split_out = 0;
    int hor_stride;
    int ver_stride;

    //int enc cfg
    ret = mpp_enc_cfg_init(&cfg);
    if (ret != 0) {
        printf("mpp init fail ret %d\n", ret);
        return -1;
    }

    bps = InitCfg->width * InitCfg->height / 8 * 30;

    hor_stride = MPP_ALIGN(InitCfg->width, 16);
    ver_stride = MPP_ALIGN(InitCfg->height, 16);

    device->width = InitCfg->width;
    device->height = InitCfg->height;
    device->fmt = InitCfg->fmt;
    device->buf_num = InitCfg->buf_num;
    mpp_enc_cfg_set_s32(cfg, "prep:width", InitCfg->width);
    mpp_enc_cfg_set_s32(cfg, "prep:height", InitCfg->height);
    mpp_enc_cfg_set_s32(cfg, "prep:hor_stride", hor_stride);
    mpp_enc_cfg_set_s32(cfg, "prep:ver_stride", ver_stride);
    mpp_enc_cfg_set_s32(cfg, "prep:format", InitCfg->fmt);

    mpp_enc_cfg_set_s32(cfg, "rc:mode", InitCfg->rc_mode);

    /* fix input / output frame rate */
    mpp_enc_cfg_set_s32(cfg, "rc:fps_in_flex", 0);
    mpp_enc_cfg_set_s32(cfg, "rc:fps_in_num", 30);
    mpp_enc_cfg_set_s32(cfg, "rc:fps_in_denorm", 1);
    mpp_enc_cfg_set_s32(cfg, "rc:fps_out_flex", 0);
    mpp_enc_cfg_set_s32(cfg, "rc:fps_out_num", 30);
    mpp_enc_cfg_set_s32(cfg, "rc:fps_out_denorm", 1);
    mpp_enc_cfg_set_s32(cfg, "rc:gop", 30 * 2);

    /* drop frame or not when bitrate overflow */
    mpp_enc_cfg_set_u32(cfg, "rc:drop_mode", MPP_ENC_RC_DROP_FRM_DISABLED);
    mpp_enc_cfg_set_u32(cfg, "rc:drop_thd", 20);        /* 20% of max bps */
    mpp_enc_cfg_set_u32(cfg, "rc:drop_gap", 1);         /* Do not continuous drop frame */

    /* setup bitrate for different rc_mode */
    mpp_enc_cfg_set_s32(cfg, "rc:bps_target", bps);
    switch (InitCfg->rc_mode)
    {
        case MPP_ENC_RC_MODE_FIXQP :
        {
            /* do not setup bitrate on FIXQP mode */
        }
        break;
        case MPP_ENC_RC_MODE_CBR :
        {
            /* CBR mode has narrow bound */
            mpp_enc_cfg_set_s32(cfg, "rc:bps_max", bps * 17 / 16);
            mpp_enc_cfg_set_s32(cfg, "rc:bps_min", bps * 15 / 16);
        }
        break;
        case MPP_ENC_RC_MODE_VBR :
        case MPP_ENC_RC_MODE_AVBR :
        {
            /* VBR mode has wide bound */
            mpp_enc_cfg_set_s32(cfg, "rc:bps_max", bps * 17 / 16);
            mpp_enc_cfg_set_s32(cfg, "rc:bps_min", bps * 1 / 16);
        }
        break;
        default :
        {
            /* default use CBR mode */
            mpp_enc_cfg_set_s32(cfg, "rc:bps_max", bps * 17 / 16);
            mpp_enc_cfg_set_s32(cfg, "rc:bps_min", bps * 15 / 16);
        }
        break;
    }

    /* setup qp for different codec and rc_mode */
    mpp_enc_cfg_set_s32(cfg, "jpeg:q_factor", 80);
    mpp_enc_cfg_set_s32(cfg, "jpeg:qf_max", 99);
    mpp_enc_cfg_set_s32(cfg, "jpeg:qf_min", 1);

    /* setup codec  */
    mpp_enc_cfg_set_s32(cfg, "codec:type", MPP_VIDEO_CodingMJPEG);

    mpp_env_get_u32("split_mode", &split_mode, MPP_ENC_SPLIT_NONE);
    mpp_env_get_u32("split_arg", &split_arg, 0);
    mpp_env_get_u32("split_out", &split_out, 0);

    ret = mpi->control(ctx, MPP_ENC_SET_CFG, cfg);
    if (ret) {
        printf("mpi control enc set cfg failed ret %d\n", ret);
    }

    return ret;
}

static int _Ven_GetFrameSize(int fmt, int hor_stride, int ver_stride)
{
    int frame_size = 0;

    switch (fmt & MPP_FRAME_FMT_MASK) {
    case MPP_FMT_YUV420SP:
    case MPP_FMT_YUV420P: {
        frame_size = MPP_ALIGN(hor_stride, 64) * MPP_ALIGN(ver_stride, 64) * 3 / 2;
    } break;

    case MPP_FMT_YUV422_YUYV :
    case MPP_FMT_YUV422_YVYU :
    case MPP_FMT_YUV422_UYVY :
    case MPP_FMT_YUV422_VYUY :
    case MPP_FMT_YUV422P :
    case MPP_FMT_YUV422SP : {
        frame_size = MPP_ALIGN(hor_stride, 64) * MPP_ALIGN(ver_stride, 64) * 2;
    } break;
    case MPP_FMT_RGB444 :
    case MPP_FMT_BGR444 :
    case MPP_FMT_RGB555 :
    case MPP_FMT_BGR555 :
    case MPP_FMT_RGB565 :
    case MPP_FMT_BGR565 :
    case MPP_FMT_RGB888 :
    case MPP_FMT_BGR888 :
    case MPP_FMT_RGB101010 :
    case MPP_FMT_BGR101010 :
    case MPP_FMT_ARGB8888 :
    case MPP_FMT_ABGR8888 :
    case MPP_FMT_BGRA8888 :
    case MPP_FMT_RGBA8888 : {
        frame_size = MPP_ALIGN(hor_stride, 64) * MPP_ALIGN(ver_stride, 64);
    } break;

    default: {
        frame_size = MPP_ALIGN(hor_stride, 64) * MPP_ALIGN(ver_stride, 64) * 4;
    } break;
    }
    return frame_size;
}

void Venc_Deinit(Venc_Device_t *device)
{
    mpp_buffer_put(device->pkg_buf);
    mpp_destroy(ctx);
    free(device);
}

Venc_Device_t *Venc_Init(Venc_Config_t *InitCfg)
{
    int ret;
    MppPollType timeout = MPP_POLL_BLOCK;
    int frame_size;
    Venc_Device_t *device = (Venc_Device_t *)malloc(sizeof(Venc_Device_t) + InitCfg->buf_num * sizeof(MppBuffer));

    // create context
    ret = mpp_create(&ctx, &mpi);
    if (ret != 0) {
        printf("failed to create mpp ret %d\n", ret);
        return NULL;
    }
    printf("create mpp ret\n");

    // create buf allocator
    ret = mpp_buffer_group_get_internal(&group, MPP_BUFFER_TYPE_DRM);
    if (ret) {
        printf("failed to get mpp buffer group ret %d\n", ret);
        goto FAIL;
    }
    frame_size = _Ven_GetFrameSize(InitCfg->fmt, InitCfg->width, InitCfg->height);
    printf("get mpp buffer group frame_size %d\n", frame_size);

    // alloc one packet buf
    ret = mpp_buffer_get(group, &device->pkg_buf, frame_size);
    if (ret) {
        printf("failed to get buffer for output packet ret %d\n", ret);
        goto FAIL;
    }
    printf("alloc pkg_buf size = %d\n", frame_size);

    //set timeout mode
    ret = mpi->control(ctx, MPP_SET_OUTPUT_TIMEOUT, &timeout);
    if (ret != 0) {
        printf("mpi control set output timeout %d ret %d\n", timeout, ret);
        goto FAIL;
    }
    printf("mpi control set output timeout %d ret %d\n", timeout, ret);

    //create jpeg enc
    ret = mpp_init(ctx, MPP_CTX_ENC, MPP_VIDEO_CodingMJPEG);
    if (ret != 0) {
        printf("mpp init fail ret %d\n", ret);
        goto FAIL;
    }
    printf("mpp init ret %d\n", ret);

    ret = venc_cfg_setup(device, InitCfg);
    if (ret != 0)
    {
        printf("venc_cfg_setup fail ret %d\n", ret);
        goto FAIL;
    }

    printf("venc init success\n");

    return device;
FAIL:
    mpp_buffer_put(device->pkg_buf);
    mpp_destroy(ctx);
    free(device);
    return NULL;
}

int Venc_EnqueueBuf(Venc_Device_t *device, int index)
{
    int ret;
    MppFrame frame = NULL;
    MppMeta meta = NULL;
    MppPacket packet = NULL;

    if (index >= device->buf_num)
    {
        printf("index is %d > %d\n", index, device->buf_num);
        return -1;
    }

    //init mpp frame
    ret = mpp_frame_init(&frame);
    if (ret) {
        printf("mpp_frame_init failed\n");
        return -1;
    }
    //printf("mpp_frame_init success\n");

    mpp_frame_set_width(frame, device->width);
    mpp_frame_set_height(frame, device->height);
    mpp_frame_set_hor_stride(frame, MPP_ALIGN(device->width, 16));
    mpp_frame_set_ver_stride(frame, MPP_ALIGN(device->height, 16));
    mpp_frame_set_fmt(frame, device->fmt);
    mpp_frame_set_eos(frame, 0);
    mpp_frame_set_buffer(frame, device->frame_buf[index]);

    //init mpp packet
    meta = mpp_frame_get_meta(frame);
    mpp_packet_init_with_buffer(&packet, device->pkg_buf);
    mpp_packet_set_length(packet, 0);
    mpp_meta_set_packet(meta, KEY_OUTPUT_PACKET, packet);
    //mpp_meta_set_buffer(meta, KEY_MOTION_INFO, p->md_info);

    //printf("start encode %d\n", index);
    //start encode
    ret = mpi->encode_put_frame(ctx, frame);
    if (ret) {
        printf(" encode put frame failed\n");
        mpp_frame_deinit(&frame);
        return -1;
    }
    //printf("end encode %d\n", index);

    //encode done,free frame
    mpp_frame_deinit(&frame);

    return 0;
}

int Venc_DequeueBuf(Venc_Device_t *device)
{
    //do nothing
    return 0;
}

int Venc_GetEsBuf(Venc_Device_t *device, Venc_EsPacket_t *Packet)
{
    int ret;
    MppPacket packet = NULL;

    //get es buf
    ret = mpi->encode_get_packet(ctx, &packet);
    if (ret || !packet) {
        printf(" encode get packet failed\n");
        return -1;
    }

    //get user addr
    Packet->data = mpp_packet_get_pos(packet);
    Packet->len = mpp_packet_get_length(packet);
    //may be wrong
    Packet->priv = packet;

    return 0;
}

int Venc_PutEsBuf(Venc_Device_t *device, Venc_EsPacket_t *Packet)
{
    mpp_packet_deinit(&Packet->priv);
    return 0;
}

